package com.luboyan.CBillbill.util;

import com.github.tobato.fastdfs.domain.fdfs.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.MetaData;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadCallback;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.luboyan.CBillbill.exception.ConditionException;
import com.mysql.cj.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * 文件上传处理类
 */
@Component
public class FastDFSUtil {

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    @Autowired
    private AppendFileStorageClient appendFileStorageClient;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String DEFAULT_GROUP = "group1"; //对应配置好的fastdfs中的组名，无设置默认就是group1

    private static final String PATH_KEY = "path-key:";

    private static final String UPLOADED_SIZE_KEY = "uploaded-size-key";

    private static final String UPLOADED_SHARD_KEY = "uploaded-shard-key";

    /**
     * 获取文件类型
     */
    public String getFileType(MultipartFile file) {
        if (file == null) {
            throw new ConditionException("非法文件！");
        }
        String fileName = file.getOriginalFilename();
        if (fileName == null) {
            throw new ConditionException("非法文件名！");
        }
        int index = fileName.lastIndexOf(".");
        return fileName.substring(index + 1);
    }

    /**
     * 上传普通文件
     */
    public String uploadCommonFile(MultipartFile file) throws Exception {
        String fileType = this.getFileType(file);
        StorePath storePath = appendFileStorageClient
                .uploadAppenderFile(DEFAULT_GROUP, file.getInputStream(), file.getSize(), fileType);
        return storePath.getPath();
    }

    /**
     * 文件分片上传
     */
    public String uploadAppendFile(MultipartFile file) throws Exception {
        String fileType = this.getFileType(file);
        StorePath storePath = appendFileStorageClient.uploadAppenderFile(DEFAULT_GROUP, file.getInputStream(), file.getSize(), fileType);
        return storePath.getPath();
    }

    /**
     * @param offset 偏移量，即下一个分片要在总体上上传的位置
     */
    public void modifyAppendFile(MultipartFile file, String filePath, long offset) throws Exception {
        appendFileStorageClient.modifyFile(DEFAULT_GROUP, filePath, file.getInputStream(), file.getSize(), offset);
    }

    /**
     * 文件分片上传
     *
     * @return 上传完毕后需要返回当前的文件路径
     * 一般可以给前端用于一些操作
     */
    @Transactional
    public String uploadFileByShard(MultipartFile file, String fileMd5, Integer shard, Integer shardTotal) throws Exception {
        if (file == null || shard == null || shardTotal == null) {
            throw new ConditionException("分片参数异常！");
        }
        String pathKey = PATH_KEY + fileMd5; //文件上传成功后返回路径，或者是第一片上传成功后返回分片路径
        String uploadedSizeKey = UPLOADED_SIZE_KEY + fileMd5; //当前已上传的分片总大小
        String uploadedShardKey = UPLOADED_SHARD_KEY + fileMd5; //当前已上传个多少分片

        //上传前先获取当前上传状态
        String uploadedSizeStr = redisTemplate.opsForValue().get(uploadedSizeKey);
        Long uploadedSize = 0L;
        if (!StringUtils.isNullOrEmpty(uploadedSizeStr)) {
            uploadedSize = Long.valueOf(uploadedSizeStr);
        }
        //进行上传
        if (shard == 1) { //第一个分片
            String filePath = this.uploadAppendFile(file);
            if (StringUtils.isNullOrEmpty(filePath)) {
                throw new ConditionException("文件路径异常！");
            }
            redisTemplate.opsForValue().set(pathKey, filePath);
            redisTemplate.opsForValue().set(uploadedShardKey, "1");
        } else {
            //如果不是第一个分片
            String filePath = redisTemplate.opsForValue().get(pathKey);
            if (StringUtils.isNullOrEmpty(filePath)) {
                throw new ConditionException("文件路径异常！");
            }
            this.modifyAppendFile(file, filePath, uploadedSize);
            redisTemplate.opsForValue().increment(uploadedShardKey); //将对应key的value+1
        }
        //修改历史上传的分片总大小
        uploadedSize += file.getSize();
        redisTemplate.opsForValue().set(uploadedSizeKey, String.valueOf(uploadedSize));

        //判断文件分片是否全部上传
        String uploadedShardKeyStr = redisTemplate.opsForValue().get(uploadedShardKey);
        Integer uploadedShard = Integer.valueOf(uploadedShardKeyStr);
        String resultPath = "";
        if (uploadedShard.equals(shardTotal)) { //判断是否所有分片上传完毕
            resultPath = redisTemplate.opsForValue().get(pathKey); //上传完毕则返回文件路径
            //文件上传完毕后清空redis中的key值
            List<String> keyList = Arrays.asList(pathKey, uploadedSizeKey, uploadedShardKey);
            redisTemplate.delete(keyList);
        }
        return resultPath;
    }

    /**
     * 删除文件
     */
    public void deleteFile(String filePath) {
        fastFileStorageClient.deleteFile(filePath);
    }

    /**
     * java后端实现文件切片
     */
    private static final int SHARD_SIZE = 1024 * 1024; //分片大小(1MB)

    public void convertFile(MultipartFile multipartFile) throws Exception {
        String fileName = multipartFile.getName();
        String fileType = this.getFileType(multipartFile);
        File file = this.multipartFileToFile(multipartFile); //生成临时文件
        long fileSize = file.length(); //获取文件总大小
        int count = 1;
        for (int i = 0; i < fileSize; i += SHARD_SIZE) { //i的单位是字节
            //java提供的一个类，可以从任意位置操作文件
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
            randomAccessFile.seek(i);
            //用一个字节数组存放数据
            byte[] bytes = new byte[SHARD_SIZE];
            //获取实际的数据长度，因为最后一个分片的长度往往不会和其他的相等
            int len = randomAccessFile.read(bytes);
            //生成文件路径
            String path = "D:/test/" + count + "." + fileType;
            //生成分片文件
            File shard = new File(path);
            //生成分片文件流
            FileOutputStream fos = new FileOutputStream(shard);
            //写入文件
            fos.write(bytes, 0, len);
            fos.close();
            randomAccessFile.close();
            count++;
        }
        file.delete();
    }

    public File multipartFileToFile(MultipartFile multipartFile) throws Exception {
        String multipartFileName = multipartFile.getOriginalFilename();
        String[] fileName = multipartFileName.split("\\.");
        File file = File.createTempFile(fileName[0], "." + fileName[1]);
        multipartFile.transferTo(file);
        return file;
    }

    /**
     * 分片下载视频,通过流进行传输
     */
    @Value("${fdfs.http.storage.addr}")
    private String httpStorageAddr;

    public void getVideosByShard(HttpServletRequest request, HttpServletResponse response, String path) throws Exception {
        //将前端传来的文件相对路径进行包装获取绝对路径
        FileInfo fileInfo = fastFileStorageClient.queryFileInfo(DEFAULT_GROUP, path);
        long totalFileSize = fileInfo.getFileSize(); //获取文件总的大小
        String url = httpStorageAddr + path; //设置绝对路径

        //设置发送给dfs服务器的请求,headers装载
        Enumeration<String> headerNames = request.getHeaderNames(); //获取请求头所有参数的名称
        Map<String, Object> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) { //判断headerNames是否还有值
            String headerName = headerNames.nextElement(); //获取每个参数名
            headers.put(headerName, request.getHeader(headerName)); //将其写入headers中
        }
        //比较关键的range参数
        String rangeStr = request.getHeader("Range");
        String[] range;
        if (StringUtils.isNullOrEmpty(rangeStr)) { //如果没有Range参数则默认无需分片
            rangeStr = "bytes=0-" + (totalFileSize - 1);
        }

        //获取分片的大小以便于写入响应体中的参数
        range = rangeStr.split("bytes=|-"); //拆成3个部分。分别时bytes=，开始，结束
        long begin = 0;
        if (range.length >= 2) {
            begin = Long.parseLong(range[1]);
        }
        long end = totalFileSize - 1;
        if (range.length >= 3) {
            end = Long.parseLong(range[2]);
        }
        long len = (end - begin) + 1;

        //设置发送给前端的响应体
        String contentRange = "bytes " + begin + "/" + end + "/" + totalFileSize;
        response.setHeader("Content-Range", contentRange);
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Type", "video/mp4");
        response.setContentLength((int) len);
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        //发起http请求，并且将流写入response
        HttpUtil.get(url, headers, response);
    }

    /**
     * 下载文件到本地服务器上
     */
    public void downLoadFile(String url, String localPath) {
        fastFileStorageClient.downloadFile(DEFAULT_GROUP, url,
                new DownloadCallback<String>() {
                    @Override
                    public String recv(InputStream ins) throws IOException {
                        File file = new File(localPath);
                        OutputStream os = new FileOutputStream(file);
                        int len = 0;
                        byte[] buffer = new byte[1024];
                        while ((len = ins.read(buffer)) != -1) {
                            os.write(buffer, 0, len);
                        }
                        os.close();
                        ins.close();
                        return "success";
                    }
                });
    }
}
